﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace Miv
{
    public partial class FormMiv : Form
    {
        public FormMiv()
        {
            InitializeComponent();
        }

        private string image_file_ext = "*.BMP;*.JPG;*.JPEG;*.GIF;*.TIFF;*.PNG";

        private bool gl_loaded = false;

        private bool fit_window = true;

        private int texture_id = 0;
        private Size texture_size;
        private Bitmap texture_image = null;

        private System.IO.MemoryStream image_mem;
        private ExifLibrary.ImageFile exif = null;

        private string image_file;
        private bool show_exif;
        private bool show_thumbnail;
        //public string Image
        //{
        //    get { return image_file; }
        //    set { image_file = value; }
        //}
        public Point Offset
        {
            get { return offset; }
            set
            {
                offset = value;

                SetupViewport();

                glctrl.Invalidate();

                if (show_thumbnail)
                {
                    pic_thumbnail.Invalidate();
                }
            }
        }
        public float ZoomRate
        {
            get { return zoom_rate; }
            set
            {
                zoom_rate = value;
                fit_window = false;

                SetupViewport();

                glctrl.Invalidate();

                if (show_exif)
                {
                    pic_thumbnail.Invalidate();
                }

                UpdateTitle();
            }
        }
        public bool ShowExif
        {
            get { return show_exif; }
            set 
            {
                show_exif = value;
                if (show_exif)
                {
                    label_info.Visible = true;
                    menu_item_exif.Checked = true;
                }
                else
                {
                    label_info.Visible = false;
                    menu_item_exif.Checked = false;
                }
            }
        }
        public bool ShowThumbnail
        {
            get { return show_thumbnail; }
            set
            {
                show_thumbnail = value;
                if (show_thumbnail)
                {
                    pic_thumbnail.Visible = true;
                    menu_item_thumbnail.Checked = true;
                }
                else
                {
                    pic_thumbnail.Visible = false;
                    menu_item_thumbnail.Checked = false;
                }
            }
        }
        public ContextMenuStrip RightClickMenu
        {
            get { return glctrl.ContextMenuStrip; }
            set
            {
                glctrl.ContextMenuStrip.Dispose();
                glctrl.ContextMenuStrip = value;
            }
        }

        public EventHandler ImageShown;
        public EventHandler Panned;
        public EventHandler NewFormCreated;
        new public KeyEventHandler KeyUp;
        new public KeyEventHandler KeyDown;

        private void FormMiv_Load(object sender, EventArgs e)
        {
            offset = new Point(0, 0);
            zoom_rate = 1.0f;

            ShowExif = true;
            ShowThumbnail = true;

            image_mem = new System.IO.MemoryStream();

            menu_item_fit_window.Checked = fit_window;

            int min_width = 400 + (this.Width - this.ClientSize.Width);
            int min_height = 300 + (this.Height - this.ClientSize.Height);

            this.MinimumSize = new Size(min_width, min_height);

            glctrl.MouseWheel += new MouseEventHandler(glctrl_MouseWheel);
        }
        private void FormMiv_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (image_loader != null)
            {
                while (image_loader.IsBusy)
                {
                    image_loader.CancelAsync();
                    Application.DoEvents();
                }
                image_loader.Dispose();
            }

            if (image_mem != null)
                image_mem.Dispose();

            if (texture_image != null)
                texture_image.Dispose();

            glctrl.MakeCurrent();
            if (GL.IsTexture(texture_id))
                GL.DeleteTexture(texture_id);

            //if (e.CloseReason == CloseReason.UserClosing)
            //{
            //    ((IFormList)(this.Owner)).Delete(this);
            //    System.GC.Collect();
            //}
        }
        private void FormMiv_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                if (ModifierKeys == Keys.Control)
                {
                    e.Effect = DragDropEffects.Copy;
                }
                else
                {
                    e.Effect = DragDropEffects.Move;
                }
            }
        }
        private void FormMiv_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

            //string image_file = @"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg";

            //if (ModifierKeys == Keys.Control)
            //{
            //    FormMiv miv = new FormMiv();
            //    miv.Show(this.Owner);
            //    miv.ShowImages(files);
            //}
            //else
            //{
            //    this.ShowImages(files);
            //}
            ShowImages(files);
        }

        private Point offset;
        private float zoom_rate;
        private Point last_pos;
        private float last_zoom_rate = 1.0f;
        private void glctrl_Load(object sender, EventArgs e)
        {
            gl_loaded = true;
        }
        private void glctrl_Paint(object sender, PaintEventArgs e)
        {
            if (!gl_loaded)
                return;

            glctrl.MakeCurrent();
            GL.Enable(EnableCap.Texture2D);
            GL.Disable(EnableCap.DepthTest | EnableCap.AlphaTest | EnableCap.Lighting);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            if (GL.IsTexture(texture_id))
            {
                GL.MatrixMode(MatrixMode.Modelview);
                GL.LoadIdentity();
                GL.BindTexture(TextureTarget.Texture2D, texture_id);

                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(0, 0);
                GL.Vertex2(0, 0);
                GL.TexCoord2(1, 0);
                GL.Vertex2(texture_size.Width, 0);
                GL.TexCoord2(1, 1);
                GL.Vertex2(texture_size.Width, texture_size.Height);
                GL.TexCoord2(0, 1);
                GL.Vertex2(0, texture_size.Height);
                GL.End();
            }

            glctrl.SwapBuffers();
        }
        private void glctrl_Resize(object sender, EventArgs e)
        {
            Redraw();
        }
        private void glctrl_MouseWheel(object sender, MouseEventArgs e)
        {
            if (!IsShowing)
                return;

            if (Math.Abs(e.Delta) > 20)
            {
                float rate = GetNextZoomRate(e.Delta);
                ZoomAt(e.Location, rate);
            }

        }
        private void glctrl_MouseDown(object sender, MouseEventArgs e)
        {
            //if (e.Button == MouseButtons.Left)
            {
                last_pos = e.Location;
            }
        }
        private void glctrl_MouseMove(object sender, MouseEventArgs e)
        {
            if ((e.Button == MouseButtons.Left) && IsShowing)
            {
                this.Cursor = Cursors.Hand;
                Shift(e.X - last_pos.X, e.Y - last_pos.Y);

                if (Panned != null)
                {
                    PannedEventArgs args = new PannedEventArgs(e.X - last_pos.X, e.Y - last_pos.Y);
                    Panned(this, args);
                }

                last_pos = e.Location;
            }
            else
            {
                this.Cursor = Cursors.Default;
            }
        }
        private void glctrl_KeyUp(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Space:
                    if (image_loader.IsBusy)
                        break;

                    if (Math.Abs(1.0f - zoom_rate) < 0.01f)
                    {
                        //ZoomRate = GetFitZoomRate();
                        ZoomAt(new Point(glctrl.ClientSize.Width / 2, glctrl.ClientSize.Height / 2), last_zoom_rate);

                        if (Math.Abs(zoom_rate - GetFitZoomRate()) < 0.001)
                        {
                            fit_window = true;
                            menu_item_fit_window.Checked = fit_window;
                        }
                    }
                    else
                    {
                        last_zoom_rate = zoom_rate;
                        ZoomAt(new Point(glctrl.ClientSize.Width / 2, glctrl.ClientSize.Height / 2), 1.0f);

                        fit_window = false;
                        menu_item_fit_window.Checked = fit_window;
                    }
                    break;
                case Keys.Left:
                    ShowNextImage(-1);
                    break;
                case Keys.Right:
                    ShowNextImage(1);
                    break;
                default:
                    if (this.KeyUp != null)
                    {
                        this.KeyUp(this, e);
                    }
                    break;
            }

        }
        private void glctrl_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                default:
                    if (this.KeyDown != null)
                    {
                        this.KeyDown(this, e);
                    }
                    break;
            }
        }
        private void glctrl_DoubleClick(object sender, EventArgs e)
        {
            if (texture_image == null)
            {
                OpenFileDialog file_dialog = new OpenFileDialog();
                file_dialog.Filter = "Image Files(*.BMP;*.JPG;*.GIF)|*.BMP;*.JPG;*.GIF|All files (*.*)|*.*";
                file_dialog.Filter = "Image FIles|" + image_file_ext + "|All files (*.*)|*.*";
                if (file_dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    ShowImage(file_dialog.FileName);
                }
                file_dialog.Dispose();
            }
        }

        private void Redraw()
        {
            if (image_loader.IsBusy)
                return;

            if (fit_window)
            {
                zoom_rate = GetFitZoomRate();
            }

            SetupViewport();
            glctrl.Invalidate();
            pic_thumbnail.Invalidate();

            UpdateTitle();
        }
        private void SetupViewport()
        {
            if (!gl_loaded)
                return;

            int w = glctrl.ClientSize.Width;
            int h = glctrl.ClientSize.Height;

            glctrl.MakeCurrent();

            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            GL.Ortho(0, w, h, 0, 1, -1); // Top-left corner pixel has coordinate (0, 0)
            //GL.Rotate(rotation, new Vector3(0f, 0f, 1f));

            CorrectOffset();
            GL.Translate(offset.X, offset.Y, 0);
            GL.Scale(zoom_rate, zoom_rate, 1.0f);

            GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
        }

        private List<string> GetImageFiles(string file_directory, string file_ext)
        {
            if (System.IO.Directory.Exists(file_directory))
            {
                List<string> dir_files = new List<string>();
                string[] all_ext = file_ext.Split(new char[] { ',', ';', ' ' });
                foreach (string ext in all_ext)
                {
                    string[] tmp = System.IO.Directory.GetFiles(file_directory, ext);
                    if (tmp.Length > 0)
                        dir_files.AddRange(tmp);
                }
                if (dir_files.Count > 0)
                {
                    dir_files.Sort();
                    return dir_files;
                }
            }
            return null;
        }
        private void LoadImage(string filename)
        {
            image_file = filename;

            using (System.IO.FileStream fs = System.IO.File.OpenRead(filename))
            {
                image_mem.SetLength(fs.Length);

                fs.Read(image_mem.GetBuffer(), 0, (int)fs.Length);
                fs.Close();
            }

            if (texture_image != null)
            {
                texture_image.Dispose();
            }

            try
            {
                texture_image = (Bitmap)Bitmap.FromStream(image_mem);
            }
            catch (Exception)
            {
                texture_image = null;
            }
        }
        private void MakeTexture(Bitmap bmp, out int id, out Size sz)
        {
            id = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, id);

            //image_size = new System.Drawing.Size(texture_bmp.Width, texture_bmp.Height);

            System.Drawing.Imaging.BitmapData bmp_data
                = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                               System.Drawing.Imaging.ImageLockMode.ReadOnly,
                               System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            sz = new System.Drawing.Size(bmp.Width, bmp.Height);

            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
                          bmp_data.Width, bmp_data.Height, 0,
                          OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
                          PixelType.UnsignedByte, bmp_data.Scan0);

            bmp.UnlockBits(bmp_data);

            // We haven't uploaded mipmaps, so disable mipmapping (otherwise the texture will not appear).
            // On newer video cards, we can use GL.GenerateMipmaps() or GL.Ext.GenerateMipmaps() to create
            // mipmaps automatically. In that case, use TextureMinFilter.LinearMipmapLinear to enable them.
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
        }
        private bool GetThumbnail()
        {
            return false;
        }
        private void AutoRotate()
        {
            if (exif == null)
                return;

            if (!exif.Properties.ContainsKey(ExifLibrary.ExifTag.Orientation))
                return;

            ExifLibrary.Orientation ori = (ExifLibrary.Orientation)exif.Properties[ExifLibrary.ExifTag.Orientation].Value;
            if ((ori == ExifLibrary.Orientation.RotatedLeft) || (ori == ExifLibrary.Orientation.RotatedRightAndMirroredVertically))
            {
                texture_image.RotateFlip(RotateFlipType.Rotate270FlipNone);
                //rotation = -90;
            }
            else if ((ori == ExifLibrary.Orientation.RotatedRight) || (ori == ExifLibrary.Orientation.RotatedLeftAndMirroredVertically))
            {
                texture_image.RotateFlip(RotateFlipType.Rotate90FlipNone);
                //rotation = 90;
            }
        }
        private void CorrectOffset()
        {
            if (glctrl.ClientSize.Width < zoom_rate * texture_size.Width)
            {
                if (offset.X < glctrl.ClientSize.Width - zoom_rate * texture_size.Width)
                    offset.X = (int)Math.Round(glctrl.ClientSize.Width - zoom_rate * texture_size.Width);
                if (offset.X > 0)
                    offset.X = 0;
            }
            else
            {
                offset.X = (int)Math.Round(glctrl.ClientSize.Width - zoom_rate * texture_size.Width) / 2;
            }

            if (glctrl.ClientSize.Height < zoom_rate * texture_size.Height)
            {
                if (offset.Y < glctrl.ClientSize.Height - zoom_rate * texture_size.Height)
                    offset.Y = (int)Math.Round(glctrl.ClientSize.Height - zoom_rate * texture_size.Height);
                if (offset.Y > 0)
                    offset.Y = 0;
            }
            else
            {
                offset.Y = (int)Math.Round(glctrl.ClientSize.Height - zoom_rate * texture_size.Height) / 2;
            }
        }

        private void UpdateExif()
        {
            if (image_loader.IsBusy)
                return;

            if (exif != null)
            {
                label_info.Text = GetExifString();
                label_info.Left = this.ClientSize.Width - label_info.Width - 10;
            }
            else
            {
                label_info.Text = "";
            }
        }
        private void UpdateTexture()
        {
            glctrl.MakeCurrent();

            if (GL.IsTexture(texture_id))
            {
                GL.DeleteTexture(texture_id);
                texture_id = 0;
            }

            if ((texture_image != null) && (!image_loader.IsBusy))
            {
                MakeTexture(texture_image, out texture_id, out texture_size);
            }
        }
        private void UpdateThumbnail()
        {
            Image thumbnail_image = pic_thumbnail.Image;
            pic_thumbnail.Image = null;

            if (thumbnail_image != null)
            {
                thumbnail_image.Dispose();
                thumbnail_image = null;
            }

            if ((image_loader.IsBusy) || (texture_image == null))
            {
                return;
            }
            else
            {
                int thumbnail_width = 120;
                int thumbnail_height = thumbnail_width * texture_image.Height / texture_image.Width;

                thumbnail_image = texture_image.GetThumbnailImage(thumbnail_width, thumbnail_height,
                                                     new System.Drawing.Image.GetThumbnailImageAbort(GetThumbnail), IntPtr.Zero);
                if (thumbnail_image != null)
                {
                    pic_thumbnail.Image = thumbnail_image;
                    pic_thumbnail.Top = this.ClientSize.Height - pic_thumbnail.Height - 10;
                    pic_thumbnail.Left = this.ClientSize.Width - pic_thumbnail.Width - 10;
                }
                //thumbnail_image.Dispose();
            }
        }
        private void UpdateTitle()
        {
            if (((image_file == null)) || (image_file == ""))
            {
                this.Text = Application.ProductName;
            }
            else if (IsShowing)
            {
                int percent = (int)Math.Round(100 * zoom_rate);
                this.Text = System.IO.Path.GetFileName(image_file) + "  " + percent.ToString() + "%";
            }
        }

        #region zoom rate table
        private float[] zoom_rate_table = new float[]{0.01f,
                                                      0.02f,
                                                      0.03f,
                                                      0.05f,
                                                      0.06f,
                                                      0.07f,
                                                      0.08f,
                                                      0.10f,
                                                      0.15f,
                                                      0.20f,
                                                      0.25f,
                                                      0.30f,
                                                      0.45f,
                                                      0.40f,
                                                      0.50f,
                                                      0.60f,
                                                      0.70f,
                                                      0.80f,
                                                      0.90f,
                                                      1.00f,
                                                      1.10f,
                                                      1.20f,
                                                      1.50f,
                                                      2.00f,
                                                      3.00f,
                                                      4.00f,
                                                      5.00f,
                                                      6.00f,
                                                      8.00f,
                                                      10.0f,
                                                      12.0f,
                                                      16.0f
                                                      };
        #endregion
        private float GetFitZoomRate()
        {
            if (IsShowing)
            {
                float h_rate = 1.0f * glctrl.ClientSize.Width / texture_size.Width;
                float v_rate = 1.0f * glctrl.ClientSize.Height / texture_size.Height;

                return Math.Min(h_rate, v_rate);
            }
            else
            {
                return 1.0f;
            }
        }
        private float GetNextZoomRate(int delta)
        {
            int idx = 0;
            float next_zoom_rate = zoom_rate;

            if (delta > 0)
            {
                while (idx < zoom_rate_table.Length - 1)
                {
                    if (zoom_rate_table[idx] > zoom_rate)
                        break;
                    idx++;
                }

                next_zoom_rate = zoom_rate_table[idx];

                float fit_zoom_rate = GetFitZoomRate();
                if ((fit_zoom_rate > zoom_rate) && (fit_zoom_rate < zoom_rate_table[idx]))
                {
                    next_zoom_rate = fit_zoom_rate;
                    fit_window = true;
                    menu_item_fit_window.Checked = fit_window;
                }
                else
                {
                    fit_window = false;
                    menu_item_fit_window.Checked = fit_window;
                }

            }
            else
            {
                idx = zoom_rate_table.Length - 1;
                while (idx > 0)
                {
                    if (zoom_rate_table[idx] < zoom_rate)
                        break;
                    idx--;
                }

                next_zoom_rate = zoom_rate_table[idx];

                float fit_zoom_rate = GetFitZoomRate();
                if ((fit_zoom_rate < zoom_rate) && (fit_zoom_rate > zoom_rate_table[idx]))
                {
                    next_zoom_rate = fit_zoom_rate;
                    fit_window = true;
                    menu_item_fit_window.Checked = fit_window;
                }
                else
                {
                    fit_window = false;
                    menu_item_fit_window.Checked = fit_window;
                }
            }
            return next_zoom_rate;
        }
        private string GetExifString()
        {
            if ((exif == null) || (exif.Properties.Count == 0))
            {
                return "Exif: Empty";
            }

            string exif_string = "";
            int n = 0;
            
            foreach (ExifLibrary.ExifProperty exif_item in exif.Properties)
            {
                #region convert exif to string
                switch (exif_item.Tag)
                {
                    case ExifLibrary.ExifTag.FNumber:
                        {
                            ExifLibrary.ExifURational r = exif_item as ExifLibrary.ExifURational;
                            if (r != null)
                            {
                                exif_string += exif_item.Name + ": " + "F" + r.ToFloat().ToString("F1") + Environment.NewLine;
                                n++;
                            }
                        }
                        break;
                    case ExifLibrary.ExifTag.ExposureBiasValue:
                        {
                            ExifLibrary.ExifSRational r = exif_item as ExifLibrary.ExifSRational;
                            if (r != null)
                            {
                                exif_string += exif_item.Name + ": " + r.ToFloat().ToString("F1") + "EV" + Environment.NewLine;
                                n++;
                            }
                        }
                        break;
                    case ExifLibrary.ExifTag.FocalLength:
                        {
                            ExifLibrary.ExifURational r = exif_item as ExifLibrary.ExifURational;
                            if (r != null)
                            {
                                exif_string += exif_item.Name + ": " + r.ToFloat().ToString("F1") + "mm" + Environment.NewLine;
                                n++;
                            }
                        }
                        break;
                    case ExifLibrary.ExifTag.ExposureTime:
                    case ExifLibrary.ExifTag.ShutterSpeedValue:
                        {
                            ExifLibrary.ExifURational r = exif_item as ExifLibrary.ExifURational;
                            if (r != null)
                            {
                                if (r.Value.Numerator > r.Value.Denominator)
                                    exif_string += exif_item.Name + ": " + r.ToFloat().ToString("F1") + Environment.NewLine;
                                else
                                    exif_string += exif_item.Name + ": " + exif_item.ToString() + Environment.NewLine;
                                n++;
                            }
                        }
                        break;
                    case ExifLibrary.ExifTag.DateTimeDigitized:
                        exif_string += exif_item.ToString() + Environment.NewLine;
                        n++;
                        break;
                    case ExifLibrary.ExifTag.Model:
                    case ExifLibrary.ExifTag.DocumentName:
                    case ExifLibrary.ExifTag.ISOSpeedRatings :
                    case ExifLibrary.ExifTag.PixelXDimension:
                    case ExifLibrary.ExifTag.PixelYDimension:
                    case ExifLibrary.ExifTag.ColorSpace:
                    case ExifLibrary.ExifTag.Orientation:
                    case ExifLibrary.ExifTag.BitsPerSample:
                        exif_string += exif_item.Name + ": " + exif_item.ToString() + Environment.NewLine;
                        n++;
                        break;
                    default:
                        //exif_string += exif_item.Name + ": " + exif_item.ToString() + Environment.NewLine;
                        break;
                }
                #endregion
            }

            if (n == 0)
            {
                exif_string = "Exif: Empty";
            }

            return exif_string;
        }

        public void Clear()
        {
            if (image_loader != null)
            {
                while (image_loader.IsBusy)
                {
                    image_loader.CancelAsync();
                    Application.DoEvents();
                }
            }

            if (image_mem != null)
            {
                image_mem.SetLength(0);
            }

            if (texture_image != null)
            {
                texture_image.Dispose();
                texture_image = null;
            }

            glctrl.MakeCurrent();

            if (GL.IsTexture(texture_id))
            {
                GL.DeleteTexture(texture_id);
            }

            image_file = "";
            exif = null;

            UpdateExif();
            UpdateThumbnail();

            Redraw();
        }
        public bool IsShowing
        {
            get
            {
                glctrl.MakeCurrent();
                return (GL.IsTexture(texture_id));
            }
        }
        public void ShowImage(string image_path)
        {
            image_file = image_path;
            this.Text = System.IO.Path.GetFileName(image_path) + "...";

            while (image_loader.IsBusy)
            {
                image_loader.CancelAsync();
                Application.DoEvents();
            }

            image_loader.RunWorkerAsync();

        }
        public void ShowImages(string[] image_paths)
        {
            if ((image_paths != null) && (image_paths.Length >= 1))
            {
                for (int i = 0; i < image_paths.Length && i < 4; i++)
                {
                    if ((i == 0) && (ModifierKeys != Keys.Control))
                    {
                        this.ShowImage(image_paths[i]);
                    }
                    else
                    {
                        FormMiv miv = new FormMiv();
                        miv.Show();
                        //miv.Show(this.Owner);
                        miv.ShowImage(image_paths[i]);

                        if (NewFormCreated != null)
                        {
                            NewFormCreated(miv, null);
                        }
                    }
                }
            }
        }
        public void ShowNextImage(int step)
        {
            List<string> image_files = GetImageFiles(System.IO.Path.GetDirectoryName(image_file), image_file_ext);
            int current = image_files.IndexOf(image_file);

            if ((current + step) >= 0 && (current + step) < image_files.Count)
            {
                ShowImage(image_files[current + step]);
            }
            else
            {
                //
            }
        }
        public void Shift(int dx, int dy)
        {
            Offset = new Point(offset.X + dx, offset.Y + dy);
        }
        public void ZoomAt(Point center, float rate)
        {
            float last_zoom = zoom_rate;
            zoom_rate = rate < 0.001f ? 0.001f : (rate > 100.0f ? 100.0f : rate);

            int nx = (int)Math.Round((offset.X - center.X) * zoom_rate / last_zoom + center.X);
            int ny = (int)Math.Round((offset.Y - center.Y) * zoom_rate / last_zoom + center.Y);

            offset = new Point(nx, ny);

            SetupViewport();
            glctrl.Invalidate();
            pic_thumbnail.Invalidate();

            UpdateTitle();
        }

        private void pic_thumbnail_Paint(object sender, PaintEventArgs e)
        {
            if (pic_thumbnail.Image != null)
            {
                int cw = pic_thumbnail.ClientSize.Width;
                int ch = pic_thumbnail.ClientSize.Height;

                int x = (int)(-cw * offset.X / (zoom_rate * texture_size.Width));
                int y = (int)(-ch * offset.Y / (zoom_rate * texture_size.Height));
                int w = (int)(cw * glctrl.ClientSize.Width / (zoom_rate * texture_size.Width));
                int h = (int)(ch * glctrl.ClientSize.Height / (zoom_rate * texture_size.Height));
                w = w > cw - 1 ? cw - 1 : w;
                h = h > ch - 1 ? ch - 1 : h;

                x = x < 0 ? 0 : x;
                y = y < 0 ? 0 : y;
                x = x > cw - w - 1 ? cw - w - 1 : x;
                y = y > ch - h - 1 ? ch - h - 1 : y;

                Pen dash_pen = new Pen(Color.Black);
                dash_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
                e.Graphics.DrawRectangle(Pens.White, new Rectangle(x, y, w, h));
                e.Graphics.DrawRectangle(dash_pen, new Rectangle(x, y, w, h));
                dash_pen.Dispose();

                e.Graphics.DrawRectangle(Pens.LightCyan, new Rectangle(0, 0, cw - 1, ch - 1));
            }

        }
        private void pic_thumbnail_MouseClick(object sender, MouseEventArgs e)
        {
            if (pic_thumbnail.Image == null)
                return;

            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                ShowThumbnail = false;
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                int cw = pic_thumbnail.ClientSize.Width;
                int ch = pic_thumbnail.ClientSize.Height;

                int w = (int)(cw * glctrl.ClientSize.Width / (zoom_rate * texture_size.Width));
                int h = (int)(ch * glctrl.ClientSize.Height / (zoom_rate * texture_size.Height));
                w = w > cw - 1 ? cw - 1 : w;
                h = h > ch - 1 ? ch - 1 : h;

                int x = (int)(-(e.X - w / 2) * (zoom_rate * texture_size.Width) / cw);
                int y = (int)(-(e.Y - h / 2) * (zoom_rate * texture_size.Height) / ch);

                Shift(x - offset.X, y - offset.Y);
            }
        }
        private void pic_thumbnail_MouseMove(object sender, MouseEventArgs e)
        {
            if (pic_thumbnail.Image == null)
                return;

            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                int cw = pic_thumbnail.ClientSize.Width;
                int ch = pic_thumbnail.ClientSize.Height;

                int w = (int)(cw * glctrl.ClientSize.Width / (zoom_rate * texture_size.Width));
                int h = (int)(ch * glctrl.ClientSize.Height / (zoom_rate * texture_size.Height));
                w = w > cw - 1 ? cw - 1 : w;
                h = h > ch - 1 ? ch - 1 : h;

                int x = (int)(- (e.X - w / 2) * (zoom_rate * texture_size.Width) / cw);
                int y = (int)(- (e.Y - h / 2) * (zoom_rate * texture_size.Height) / ch);

                Shift(x - offset.X, y - offset.Y);
            }
        }
        private void label_info_MouseClick(object sender, MouseEventArgs e)
        {
            if (exif == null)
                return;

            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                ShowExif = false;
            }
        }

        private void menu_item_rotate_left_Click(object sender, EventArgs e)
        {
            if (texture_image == null)
                return;

            texture_image.RotateFlip(RotateFlipType.Rotate270FlipNone);
            UpdateThumbnail();

            UpdateTexture();
            Redraw();
        }
        private void menu_item_rotate_right_Click(object sender, EventArgs e)
        {
            if (texture_image == null)
                return;

            texture_image.RotateFlip(RotateFlipType.Rotate90FlipNone);
            UpdateThumbnail();

            UpdateTexture();
            Redraw();
        }
        private void menu_item_flip_horiz_Click(object sender, EventArgs e)
        {
            if (texture_image == null)
                return;

            texture_image.RotateFlip(RotateFlipType.RotateNoneFlipX);
            UpdateThumbnail();

            UpdateTexture();
            Redraw();
        }
        private void menu_item_flip_vert_Click(object sender, EventArgs e)
        {
            if (texture_image == null)
                return;

            texture_image.RotateFlip(RotateFlipType.RotateNoneFlipY);
            UpdateThumbnail();

            UpdateTexture();
            Redraw();
        }
        private void menu_item_thumbnail_Click(object sender, EventArgs e)
        {
            ShowThumbnail = !menu_item_thumbnail.Checked;
        }
        private void menu_item_exif_Click(object sender, EventArgs e)
        {
            ShowExif = !menu_item_exif.Checked;
        }
        private void menu_item_fit_window_Click(object sender, EventArgs e)
        {
            menu_item_fit_window.Checked = !menu_item_fit_window.Checked;
            fit_window = menu_item_fit_window.Checked;
            Redraw();
        }
        private void menu_item_actual_Click(object sender, EventArgs e)
        {
            menu_item_fit_window.Checked = false;
            fit_window = menu_item_fit_window.Checked;

            ZoomAt(last_pos, 1.0f);
            //zoom_rate = 1.0f;
            //Redraw();
        }

        private void image_loader_DoWork(object sender, DoWorkEventArgs e)
        {
            image_loader.ReportProgress(0);

            bool load_completed = false;

            if ((image_file != null) && System.IO.File.Exists(image_file))
            {
                System.IO.FileStream fs;
                try
                {
                    fs = System.IO.File.OpenRead(image_file);
                }
                catch
                {
                    fs = null;
                }

                if (fs != null)
                {
                    try
                    {
                        image_mem.SetLength(fs.Length);
                        image_mem.Seek(0, System.IO.SeekOrigin.Begin);

                        int unit = 1 * 1024 * 1024;
                        int bytes_remain = (int)fs.Length;
                        int bytes_read = 0;

                        while (bytes_remain > 0)
                        {
                            if (image_loader.CancellationPending)
                            {
                                break;
                            }
                            int m = unit < bytes_remain ? unit : bytes_remain;
                            int n = fs.Read(image_mem.GetBuffer(), bytes_read, m);
                            bytes_read += n;
                            bytes_remain -= n;
                            image_loader.ReportProgress((int)(95.0f * bytes_read / fs.Length));
                            if (bytes_remain == 0)
                            {
                                load_completed = true;
                            }
                        }
                    }
                    finally
                    {
                        fs.Dispose();
                    }
                }
            }
            
            if (!load_completed)
            {
                image_mem.SetLength(0);
                image_loader.ReportProgress(50);
            }

            if (texture_image != null)
            {
                texture_image.Dispose();
                texture_image = null;
            }

            if ((!image_loader.CancellationPending) && load_completed)
            {
                try
                {
                    texture_image = (Bitmap)Bitmap.FromStream(image_mem);
                }
                catch (Exception)
                {
                    texture_image = null;
                }
            }

            image_loader.ReportProgress(100);

            if ((!image_loader.CancellationPending) && load_completed)
            {
                exif = ExifLibrary.ImageFile.FromStream(image_mem);
            }
            else
            {
                exif = null;
            }

            //image_mem.SetLength(0);
            
        }
        private void image_loader_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage < 100)
            {
                progress_bar.Visible = true;
                progress_bar.Value = e.ProgressPercentage;
            }
        }
        private void image_loader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            progress_bar.Visible = false;

            AutoRotate();

            UpdateExif();
            UpdateTexture();
            UpdateThumbnail();

            Redraw();
            GC.Collect();

            if (ImageShown != null)
            {
                ImageShown(this, null);
            }
        }




    }

    public class PannedEventArgs : EventArgs
    {
        public PannedEventArgs(int dx, int dy)
        {
            X = dx;
            Y = dy;
        }
        public int X { get; set; }
        public int Y { get; set; }
    }

}
